/*
	Leola Programming Language
	Author: Tony Sparks
	See license.txt
*/
package leola.vm.lang.collection;

import java.util.List;
import java.util.Map;

import leola.vm.Leola;
import leola.vm.exceptions.LeolaRuntimeException;
import leola.vm.lib.LeolaIgnore;
import leola.vm.lib.LeolaLibrary;
import leola.vm.types.LeoArray;
import leola.vm.types.LeoFunction;
import leola.vm.types.LeoMap;
import leola.vm.types.LeoNativeFunction;
import leola.vm.types.LeoNull;
import leola.vm.types.LeoObject;
import leola.vm.types.LeoString;
import leola.vm.util.LeoTypeConverter;



/**
 * Base collections API
 * 
 * @author Tony
 *
 */
public class CollectionsLeolaLibrary implements LeolaLibrary {

	private Leola runtime;
	
	/* (non-Javadoc)
	 * @see leola.frontend.LeolaLibrary#init(leola.frontend.Leola)
	 */
	@LeolaIgnore
	public void init(Leola leola) throws Exception {
		this.runtime = leola;
		leola.loadNativesGlobal(this);
		leola.eval(CollectionsLeolaLibrary.class.getResourceAsStream("collections.leola"));
	}


	/**
	 * Converts the {@link List} to a {@link LeoArray}
	 *
	 * @param list
	 * @return
	 */
	public LeoObject toArray(List<Object> list) {
		LeoArray result = new LeoArray(list.size());
		for(Object obj : list) {
			result.$add(LeoTypeConverter.convertToLeolaType(obj));
		}
		
		return result;
	}

	/**
	 * Converts the {@link Map} to a {@link LeoMap}
	 *
	 * @param map
	 * @return
	 */
	public LeoObject toMap(Map<LeoObject, LeoObject> map) {
		return new LeoMap(map);
	}

	/**
	 * Iterates through the array invoking the call back.
	 *
	 * @param array
	 * @param function
	 * @throws Exception
	 */
	public final LeoObject foreach(LeoObject array, LeoObject function) throws Exception {
		LeoObject r = LeoNull.LEONULL;
		switch(array.getType()) {
			case ARRAY: {
				LeoArray a = array.as();
				List<LeoObject> list = a.getArray();
				int size = list.size();
		
				if (function.isNativeFunction()) {
					LeoNativeFunction nfun = function.as();
					for(int i = 0; i < size; i++) {
						LeoObject result = nfun.call(new LeoObject[] {list.get(i)});	
						if ( LeoObject.isTrue(result) ) {
							r = result;
							break;
						}
					}
				}
				else {
					LeoFunction fun = function.as();
					for(int i = 0; i < size; i++) {
						LeoObject result = this.runtime.execute(fun, list.get(i));	
						if ( LeoObject.isTrue(result) ) {
							r = result;
							break;
						}
					}
				}
				break;
			}
			case MAP: {
				LeoMap map = array.as();
				if (function.isNativeFunction()) {
					LeoNativeFunction nfun = function.as();
					for(Map.Entry<LeoObject, LeoObject> entry : map.getMap().entrySet()) {
						LeoObject result = nfun.call(new LeoObject[] { entry.getKey(), entry.getValue() });	
						if ( LeoObject.isTrue(result) ) {
							r = result;
							break;
						}
					}
				}
				else {
					LeoFunction fun = function.as();
					for(Map.Entry<LeoObject, LeoObject> entry : map.getMap().entrySet()) {
						LeoObject result = this.runtime.execute(fun, entry.getKey(), entry.getValue() );	
						if ( LeoObject.isTrue(result) ) {
							r = result;
							break;
						}
					}
				}
				break;
			}
			case STRING: {
				LeoString str = array.as();
				int size = str.length();

				if (function.isNativeFunction()) {
					LeoNativeFunction nfun = function.as();
					for(int i = 0; i < size; i++) {
						LeoObject result = nfun.call(new LeoObject[] { str.charAt(i) });	
						if ( LeoObject.isTrue(result) ) {
							r = result;
							break;
						}
					}
				}
				else {
					LeoFunction fun = function.as();
					for(int i = 0; i < size; i++) {
						LeoObject result = this.runtime.execute(fun, str.charAt(i) );	
						if ( LeoObject.isTrue(result) ) {
							r = result;
							break;
						}
					}
				}
				break;
			}
		}
		
		return r;
	}

	/**
	 * Iterates through the array.
	 *
	 * @param interpreter
	 * @param start
	 * @param end
	 * @param function
	 * @throws Exception
	 */
	public void __for(int start, int end, LeoArray array, LeoObject function) throws Exception {
		List<LeoObject> list = array.getArray();

		if ( start < 0 || end > list.size()) {
			throw new LeolaRuntimeException("Invalid array index: " + start + " to " + end + "[Size of array: " + list.size() + "]");
		}

		if (function.isNativeFunction()) {
			LeoNativeFunction nfun = function.as();
			for(int i = start; i < end; i++) {
				LeoObject result = nfun.call(new LeoObject[] {list.get(i)});	
				if ( LeoObject.isTrue(result) ) {
					break;
				}
			}
		}
		else {
			LeoFunction fun = function.as();
			for(int i = start; i < end; i++) {
				LeoObject result = this.runtime.execute(fun, list.get(i));	
				if ( LeoObject.isTrue(result) ) {
					break;
				}
			}
		}
	}

	/**
	 * Map Reduce function
	 *
	 * @param map
	 * @param function
	 * @throws Exception
	 */
	public void mapto(LeoMap map, LeoObject function) throws Exception {
		Map<LeoObject, LeoObject> aMap = map.getMap();

		if (function.isNativeFunction()) {
			LeoNativeFunction nfun = function.as();
			for(Map.Entry<LeoObject, LeoObject> entry : aMap.entrySet()) {
				LeoObject result = nfun.call(new LeoObject[] {entry.getKey(), entry.getValue()});	
				if ( LeoObject.isTrue(result) ) {
					break;
				}
			}
		}
		else {
			LeoFunction fun = function.as();
			for(Map.Entry<LeoObject, LeoObject> entry : aMap.entrySet()) {
				LeoObject result = this.runtime.execute(fun, entry.getKey(), entry.getValue());	
				if ( LeoObject.isTrue(result) ) {
					break;
				}
			}
		}
	}

	/**
	 * Iterates through the array invoking the call back filling the
	 * array
	 *
	 * @param array
	 * @param function
	 * @throws Exception
	 */
	public final void fill(LeoArray array, LeoObject function) throws Exception {
		List<LeoObject> list = array.getArray();
		int size = list.size();

		if (function.isNativeFunction()) {
			LeoNativeFunction nfun = function.as();
			for(int i = 0; i < size; i++) {
				LeoObject result = nfun.call(new LeoObject[] {});	
				list.set(i, result);
			}
		}
		else {
			LeoFunction fun = function.as();
			for(int i = 0; i < size; i++) {
				LeoObject result = this.runtime.execute(fun);	
				list.set(i, result);
			}
		}
	}
}

